package net.scala.chapter2.o

// Compile in sbt
// Run in sbt>run get https://raw.githubusercontent.com/nraychaudhuri/scalainaction/master/chap02/breakable.scala
//
// The command line in sbt is 
// >run (post | get | delete | options) -d <request parameters comma separated -h <headers comma separated> <url>
// at minimum you should specify action(post, get, delete, options) and url

import java.security.cert.X509Certificate
import javax.net.ssl._

import org.apache.http.client.entity.UrlEncodedFormEntity
import org.apache.http.client.methods._
import org.apache.http.conn.scheme.Scheme
import org.apache.http.conn.ssl.{SSLSocketFactory, X509HostnameVerifier}
import org.apache.http.impl.client._
import org.apache.http.message._

object RestClient extends App {

  /*解析参数*/
  def parseArgs(args: Array[String]): Map[String, List[String]] = {
    def nameValuePair(paramName: String) = {
      def values(commaSeperatedValues: String) = commaSeperatedValues.split(",").toList

      val index = args.indexWhere(_ == paramName)
      (paramName, if (index == -1) Nil else values(args(index + 1)))
    }
    Map(nameValuePair("-d"), nameValuePair("-h"))
  }

  def splitByEqual(nameValue: String): Array[String] = nameValue.split('=')

  def headers = for (nameValue <- params("-h")) yield {
    def tokens = splitByEqual(nameValue)
    new BasicHeader(tokens(0), tokens(1))
  }

  def formEntity = {
    def toJavaList(scalaList: List[BasicNameValuePair]) = {
      java.util.Arrays.asList(scalaList.toArray: _*)
    }

    def formParams = for (nameValue <- params("-d")) yield {
      def tokens = splitByEqual(nameValue)
      new BasicNameValuePair(tokens(0), tokens(1))
    }

    def formEntity = new UrlEncodedFormEntity(toJavaList(formParams), "UTF-8")
    formEntity
  }

  def handlePostRequest() = {
    val httppost = new HttpPost(url)
    headers.foreach {
      httppost.addHeader(_)
    }
    httppost.setEntity(formEntity)
    val responseBody = httpClient.execute(httppost, new BasicResponseHandler())
    println(responseBody)
  }

  def handleGetRequest() = {
    val query = params("-d").mkString("&")
    val httpget = new HttpGet(s"$url?$query")
    headers.foreach {
      httpget.addHeader(_)
    }
    val responseBody = httpClient.execute(httpget, new BasicResponseHandler())
    println(responseBody)
  }

  def handleDeleteRequest() = {
    val httpDelete = new HttpDelete(url)
    val httpResponse = httpClient.execute(httpDelete)
    println(httpResponse.getStatusLine)
  }

  def handleOptionsRequest() = {
    val httpOptions = new HttpOptions(url)
    headers.foreach {
      httpOptions.addHeader(_)
    }
    val httpResponse = httpClient.execute(httpOptions)
    println(httpOptions.getAllowedMethods(httpResponse))
  }

  /*require 函数用于抛出异常*/
  require(args.size >= 2, "at minimum you should specify action(post, get, delete, options) and url")
  val command = args.head
  val params = parseArgs(args)
  val url = args.last

  /** SNI(Server Name Indication) 问题
    * 针对https服务器会使用SNI选择证书进行发送，但是本例不支持SNI(如一些Android系统)，在SSL/TLS握手期间，
    * 服务器无法根据客户端选择哪种SNI证书发送，因此需要让其验证时为True
    */
  val xtm = new X509TrustManager {
    override def getAcceptedIssuers: Array[X509Certificate] = null
    override def checkClientTrusted(p1: Array[X509Certificate], p2: String): Unit = {}
    override def checkServerTrusted(p1: Array[X509Certificate], p2: String): Unit = {}
  }
  val hostnameVerifier = new X509HostnameVerifier {
    override def verify(p1: String, p2: SSLSocket): Unit = {}
    override def verify(p1: String, p2: X509Certificate): Unit = {}
    override def verify(p1: String, p2: Array[String], p3: Array[String]): Unit = {}
    override def verify(p1: String, p2: SSLSession): Boolean = true
  }
  //TLS1.0与SSL3.0基本上没有太大的差别，可粗略理解为TLS是SSL的继承者，但它们使用的是相同的SSLContext
  val ctx = SSLContext.getInstance("TLS")
  //使用TrustManager来初始化该上下文，TrustManager只是被SSL的Socket所使用
  ctx.init(null, Array(xtm), null)
  //创建SSLSocketFactory
  var socketFactory = new SSLSocketFactory(ctx)
  socketFactory.setHostnameVerifier(hostnameVerifier)

  val httpClient = new DefaultHttpClient()
  httpClient.getConnectionManager.getSchemeRegistry
    .register(new Scheme("https", socketFactory, 443))

  command match {
    case "post" => handlePostRequest()
    case "get" => handleGetRequest()
    case "delete" => handleDeleteRequest()
    case "options" => handleOptionsRequest()
  }

}
